/**
 * Speech Recognizer Module
 * JavaScript module providing speech recognition functionality
 */
class SpeechRecognizer {
    constructor() {
        this.ws = null;
        this.isConnected = false;
        this.isRecording = false;
        this.audioContext = null;
        this.audioStream = null;
        this.audioSource = null;
        this.processor = null;
        this.audioBuffer = [];
        this.bufferSize = 16000; // 1 second of audio data
        this.recognitionCallback = null;
    }

    /**
     * Start speech recognition - connects to WebSocket and starts recording
     * @param {string} url - Full path for WebSocket connection
     * @param {string} id - ID parameter value in the path
     * @param {function} callback - Callback function to receive recognition result data from server
     */
    async recognize(url, id, callback) {
        if (typeof callback !== 'function') {
            throw new Error('Callback must be a function');
        }
        
        this.recognitionCallback = callback;
        
        try {
            // Build WebSocket URL and connect
            const wsUrl = this.buildWebSocketUrl(url, id);
            this.ws = new WebSocket(wsUrl);
            
            // Set up WebSocket event handlers
            this.setupWebSocketHandlers();
            
            // Wait for connection to be established
            await this.waitForConnection();
            
            // Start recording immediately after connection
            await this.startRecording();
            
        } catch (error) {
            console.error('[SpeechRecognizer] Failed to start recognition:', error);
            this.cleanup();
            throw error;
        }
    }

    /**
     * Stop speech recognition - stops recording and closes WebSocket
     */
    stop() {
        try {
            // Stop recording first
            this.stopRecording();
            
            // Close WebSocket connection
            if (this.ws) {
                this.ws.close();
                this.ws = null;
            }
            
            this.isConnected = false;
            this.cleanup();
            
            console.log('[SpeechRecognizer] Recognition stopped');
            
        } catch (error) {
            console.error('[SpeechRecognizer] Error stopping recognition:', error);
        }
    }

    /**
     * Build WebSocket URL
     * @param {string} url - Base URL
     * @param {string} id - ID parameter
     * @returns {string} Complete WebSocket URL
     */
    buildWebSocketUrl(url, id) {
        // Ensure URL starts with ws:// or wss:// and handle relative/host-only forms
        if (!url.startsWith('ws://') && !url.startsWith('wss://')) {
            const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
            if (url.startsWith('/')) {
                // Absolute path on current host, e.g. "/sis/ws"
                url = `${protocol}${window.location.host}${url}`;
            } else if (/^[a-z0-9.-]+(?::\d+)?\//i.test(url)) {
                // Host-only without scheme, e.g. "example.com/sis/ws" or "example.com:8080/ws"
                url = `${protocol}${url}`;
            } else {
                // Relative path without leading slash, e.g. "sis/ws"
                url = `${protocol}${window.location.host}/${url.replace(/^\/+/, '')}`;
            }
        }
        
        // Add id parameter
        const separator = url.includes('?') ? '&' : '?';
        return `${url}${separator}id=${encodeURIComponent(id)}`;
    }

    /**
     * Set up WebSocket event handlers
     */
    setupWebSocketHandlers() {
        this.ws.onopen = () => {
            this.isConnected = true;
            console.log('[SpeechRecognizer] WebSocket connected');
        };
        
        this.ws.onclose = () => {
            this.isConnected = false;
            console.log('[SpeechRecognizer] WebSocket disconnected');
        };
        
        this.ws.onerror = (error) => {
            console.error('[SpeechRecognizer] WebSocket error:', error);
        };
        
        this.ws.onmessage = (event) => {
            try {
                let data;
                if (typeof event.data === 'string') {
                    data = JSON.parse(event.data);
                } else {
                    data = {
                        type: 'binary',
                        data: event.data,
                        message: '[Binary Data]'
                    };
                }
                
                // Only call the recognition callback with the actual recognition result data
                if (this.recognitionCallback && data) {
                    this.recognitionCallback(data);
                }
            } catch (error) {
                console.error('[SpeechRecognizer] Failed to parse message:', error);
            }
        };
    }

    /**
     * Wait for WebSocket connection to be established
     * @returns {Promise}
     */
    waitForConnection() {
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                reject(new Error('Connection timeout'));
            }, 10000); // 10 second timeout
            
            const checkConnection = () => {
                if (this.isConnected) {
                    clearTimeout(timeout);
                    resolve();
                } else if (this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING) {
                    clearTimeout(timeout);
                    reject(new Error('WebSocket connection failed'));
                } else {
                    setTimeout(checkConnection, 100);
                }
            };
            
            checkConnection();
        });
    }

    /**
     * Start recording
     * @returns {Promise} Returns Promise, resolves on success, rejects on failure
     */
    async startRecording() {
        if (!this.isConnected) {
            throw new Error('WebSocket not connected');
        }
        
        if (this.isRecording) {
            throw new Error('Already recording');
        }

        try {
            // Get microphone permission
            if (!this.audioStream) {
                this.audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
            }

            // Create audio context, set sample rate to 16kHz (commonly used for speech recognition)
            this.audioContext = new (window.AudioContext || window.webkitAudioContext)({
                sampleRate: 16000
            });
            
            this.audioSource = this.audioContext.createMediaStreamSource(this.audioStream);
            
            // Use larger buffer to reduce packet count
            this.processor = this.audioContext.createScriptProcessor(8192, 1, 1);
            
            // Clear audio data buffer
            this.audioBuffer = [];
            
            this.processor.onaudioprocess = (e) => {
                if (this.ws && this.isConnected && this.isRecording) {
                    const inputData = e.inputBuffer.getChannelData(0);
                    
                    // Convert Float32Array to Int16Array (PCM 16-bit)
                    const pcmData = new Int16Array(inputData.length);
                    for (let i = 0; i < inputData.length; i++) {
                        pcmData[i] = Math.min(1, Math.max(-1, inputData[i])) * 0x7FFF;
                    }
                    
                    // Add data to buffer
                    this.audioBuffer.push(...pcmData);
                    
                    // Send when buffer reaches 1 second of data
                    if (this.audioBuffer.length >= this.bufferSize) {
                        const dataToSend = new Int16Array(this.audioBuffer.slice(0, this.bufferSize));
                        this.ws.send(dataToSend.buffer);
                        
                        // Keep remaining data
                        this.audioBuffer = this.audioBuffer.slice(this.bufferSize);
                    }
                }
            };
            
            this.audioSource.connect(this.processor);
            this.processor.connect(this.audioContext.destination);
            
            this.isRecording = true;
            
            console.log('[SpeechRecognizer] Recording started');
            
        } catch (error) {
            console.error('[SpeechRecognizer] Failed to start recording:', error);
            throw error;
        }
    }

    /**
     * Stop recording
     */
    stopRecording() {
        if (!this.isRecording) {
            return;
        }

        // Send remaining audio data
        if (this.audioBuffer && this.audioBuffer.length > 0 && this.ws && this.isConnected) {
            const remainingData = new Int16Array(this.audioBuffer);
            this.ws.send(remainingData.buffer);
            this.audioBuffer = [];
        }

        // Clean up audio processing
        if (this.processor) {
            this.processor.disconnect();
            this.audioSource.disconnect();
            this.processor = null;
            this.audioSource = null;
        }

        if (this.audioContext) {
            this.audioContext.close();
            this.audioContext = null;
        }

        this.isRecording = false;

        console.log('[SpeechRecognizer] Recording stopped');
    }

    /**
     * Clean up resources
     */
    cleanup() {
        // Stop audio stream
        if (this.audioStream) {
            this.audioStream.getTracks().forEach(track => track.stop());
            this.audioStream = null;
        }
        
        // Clear audio buffer
        this.audioBuffer = [];
        
        // Reset callback
        this.recognitionCallback = null;
    }
}

// Create global function for recognize
window.recognize = function(url, id, callback) {
    const recognizer = new SpeechRecognizer();
    return recognizer.recognize(url, id, callback).then(() => recognizer);
};

// Create global function for stop (for backward compatibility)
window.stop = function(recognizer) {
    if (recognizer && typeof recognizer.stop === 'function') {
        recognizer.stop();
    }
};

// Export module (if ES6 modules are supported)
if (typeof module !== 'undefined' && module.exports) {
    module.exports = SpeechRecognizer;
}